//////////////////////////////////////////////
// Quaternion.js
//
//////////////////////////////////////////////

/// Class ------------------------------------
	
nkMathsTests.Quaternion = class Quaternion extends nkDebug.TestClass
{
	// Statics
	static instance = new Quaternion ("nkMathsTests.Quaternion") ;

	// Utils
	areQuatEquals (a, b, epsilon)
	{
		if (Math.abs(a._x - b._x) > epsilon && Math.abs(a._x + b._x) > epsilon)
			return false ;

		if (Math.abs(a._y - b._y) > epsilon && Math.abs(a._y + b._y) > epsilon)
			return false ;

		if (Math.abs(a._z - b._z) > epsilon && Math.abs(a._z + b._z) > epsilon)
			return false ;

		if (Math.abs(a._w - b._w) > epsilon && Math.abs(a._w + b._w) > epsilon)
			return false ;

		return true ;
	}

	getVectorConstructor (managed)
	{
		return managed ? nkMaths.Vector : nkMaths.unmanaged.Vector ;
	}

	getQuaternionConstructor (managed)
	{
		return managed ? nkMaths.Quaternion : nkMaths.unmanaged.Quaternion ;
	}

	getMatrixConstructor (managed)
	{
		return managed ? nkMaths.Matrix : nkMaths.unmanaged.Matrix ;
	}

	// Constructors
	testDefaultConstructor (managed)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const q = new ctor () ;

		nkDebug.TestUtils.areNumbersEqual(q._x, 0, 0, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 1, 0, "Wrong W") ;

		if (!managed)
			q.delete() ;
	}

	testArgsConstructor (managed)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const q = new ctor (2, 3, 4, 5) ;

		nkDebug.TestUtils.areNumbersEqual(q._x, 2, 0, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 3, 0, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 4, 0, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 5, 0, "Wrong W") ;

		if (!managed)
			q.delete() ;
	}

	testAxisAngleConstructor (managed, managedV)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const ctorV = Quaternion.instance.getVectorConstructor(managedV) ;
		const v = new ctorV (1, 0, 0) ;
		const radians90 = 90 * nkMaths.MathConstants.PI / 180 ;
		const q = new ctor (v, radians90) ;

		nkDebug.TestUtils.areNumbersEqual(q._x, 0.7071068, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		if (!managed)
			q.delete() ;

		if (!managedV)
			v.delete() ;
	}

	testEulerConstructor (managed, managedV)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const ctorV = Quaternion.instance.getVectorConstructor(managedV) ;
		const radians90 = 90 * nkMaths.MathConstants.PI / 180 ;
		const v = new ctorV (radians90, 0, 0) ;
		const q = new ctor (v) ;

		nkDebug.TestUtils.areNumbersEqual(q._x, 0.7071068, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		if (!managed)
			q.delete() ;

		if (!managedV)
			v.delete() ;
	}

	testMatrixConstructor (managed, managedM)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const ctorM = Quaternion.instance.getMatrixConstructor(managedM) ;
		const radians90 = 90 * nkMaths.MathConstants.PI / 180 ;
		const cosRad90 = Math.cos(radians90) ;
		const sinRad90 = Math.sin(radians90) ;

		const m = new ctorM 
		(
			1, 0, 0, 1,
			0, cosRad90, -sinRad90, 1,
			0, sinRad90, cosRad90, 1,
			0, 0, 0, 1
		) ;

		const q = new ctor (m) ;

		nkDebug.TestUtils.areNumbersEqual(q._x, 0.7071068, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		if (!managed)
			q.delete() ;

		if (!managedM)
			m.delete() ;
	}

	testCopyConstructor (managed0, managed1)
	{
		const ctor0 = Quaternion.instance.getQuaternionConstructor(managed0) ;
		const ctor1 = Quaternion.instance.getQuaternionConstructor(managed1) ;
		const q0 = new ctor1 (2, 3, 4, 5) ;
		const q = new ctor0 (q0) ;

		nkDebug.TestUtils.areNumbersEqual(q._x, 2, 0, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 3, 0, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 4, 0, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 5, 0, "Wrong W") ;

		if (!managed0)
			q.delete() ;

		if (!managed1)
			q0.delete() ;
	}

	// Getters
	testGetAsEuler (managed)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const q = new ctor (0.7071068, 0, 0, 0.7071068) ;

		const eManaged = q.getAsEulerAngles() ;
		nkDebug.TestUtils.check(eManaged instanceof nkMaths.Vector, "Not an instance of managed vector") ;
		const qTestM = new nkMaths.Quaternion (eManaged) ;
		nkDebug.TestUtils.check(Quaternion.instance.areQuatEquals(q, qTestM, 0.00001), "Bad quaternion reconstruction from managed") ;

		const eUnmanaged = q.getAsEulerAngles_u() ;
		nkDebug.TestUtils.check(eUnmanaged instanceof nkMaths.unmanaged.Vector, "Not an instance of unmanaged vector") ;
		const qTestU = new nkMaths.Quaternion (eUnmanaged) ;
		nkDebug.TestUtils.check(Quaternion.instance.areQuatEquals(q, qTestU, 0.00001), "Bad quaternion reconstruction from unmanaged") ;
		eUnmanaged.delete() ;

		if (!managed)
			q.delete() ;
	}

	// Setters
	testSetFromEuler (managed, managedV)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const ctorV = Quaternion.instance.getVectorConstructor(managedV) ;
		const q = new ctor () ;
		const v = new ctorV () ;
		const radians30 = 30 * nkMaths.MathConstants.PI / 180 ;
		const radians90 = 90 * nkMaths.MathConstants.PI / 180 ;

		v.assign(new nkMaths.Vector (radians90, 0, 0)) ;
		q.setFromEuler(v) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.7071068, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (0, radians90, 0)) ;
		q.setFromEuler(v) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.7071068, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (0, 0, radians90)) ;
		q.setFromEuler(v) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.7071068, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (radians30, radians30, 0)) ;
		q.setFromEuler(v) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.25, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.25, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, -0.0669873, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.9330127, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (radians30, 0, radians30)) ;
		q.setFromEuler(v) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.25, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.0669873, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.25, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.9330127, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (0, radians30, radians30)) ;
		q.setFromEuler(v) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, -0.0669873, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.25, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.25, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.9330127, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (radians30, radians30, radians30)) ;
		q.setFromEuler(v) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.1767767, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.3061862, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.1767767, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.9185587, 0.0001, "Wrong W") ;

		if (!managed)
			q.delete() ;
		
		if (!managedV)
			v.delete() ;
	}

	testSetFromAxisAngle (managed, managedV)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const ctorV = Quaternion.instance.getVectorConstructor(managedV) ;
		const q = new ctor () ;
		const v = new ctorV () ;
		const radians30 = 30 * nkMaths.MathConstants.PI / 180 ;
		const radians90 = 90 * nkMaths.MathConstants.PI / 180 ;

		v.assign(new nkMaths.Vector (1, 0, 0)) ;
		q.setFromAxisAngle(v, radians90) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.7071068, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (0, 1, 0)) ;
		q.setFromAxisAngle(v, radians90) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.7071068, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (0, 0, 1)) ;
		q.setFromAxisAngle(v, radians90) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.7071068, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (1, 1, 0)).normalizeVec3() ;
		q.setFromAxisAngle(v, radians30) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.1830127, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.1830127, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.9659258, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (1, 0, 1)).normalizeVec3() ;
		q.setFromAxisAngle(v, radians30) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.1830127, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.1830127, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.9659258, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (0, 1, 1)).normalizeVec3() ;
		q.setFromAxisAngle(v, radians30) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.1830127, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.1830127, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.9659258, 0.0001, "Wrong W") ;

		v.assign(new nkMaths.Vector (1, 1, 1)).normalizeVec3() ;
		q.setFromAxisAngle(v, radians30) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.1494292, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.1494292, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.1494292, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.9659258, 0.0001, "Wrong W") ;

		if (!managed)
			q.delete() ;

		if (!managedV)
			v.delete() ;
	}

	testSetFromMatrix (managed, managedM)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const ctorM = Quaternion.instance.getMatrixConstructor(managedM) ;
		const q = new ctor () ;
		const m = new ctorM () ;
		const radians30 = 30 * nkMaths.MathConstants.PI / 180 ;
		const cos30 = Math.cos(radians30) ;
		const sin32 = Math.sin(radians30) ;
		const radians90 = 90 * nkMaths.MathConstants.PI / 180 ;
		const cos90 = Math.cos(radians90) ;
		const sin90 = Math.sin(radians90) ;

		m.assign(new nkMaths.Matrix
			(
				1, 0, 0, 1,
				0, cos90, -sin90, 1,
				0, sin90, cos90, 1,
				0, 0, 0, 1
			)
		) ;
		q.setFromOrientationMatrix(m) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0.7071068, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		m.assign(new nkMaths.Matrix
			(
				cos90, 0, sin90, 1,
				0, 1, 0, 1,
				-sin90, 0, cos90, 1,
				0, 0, 0, 1
			)
		) ;
		q.setFromOrientationMatrix(m) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0.7071068, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		m.assign(new nkMaths.Matrix
			(
				cos90, -sin90, 0, 1,
				sin90, cos90, 0, 1,
				0, 0, 1, 1,
				0, 0, 0, 1
			)
		) ;
		q.setFromOrientationMatrix(m) ;
		nkDebug.TestUtils.areNumbersEqual(q._x, 0, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(q._y, 0, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(q._z, 0.7071068, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(q._w, 0.7071068, 0.0001, "Wrong W") ;

		if (!managed)
			q.delete() ;

		if (!managedM)
			m.delete() ;
	}

	// Transformations
	testTransformVector (managedQ, managedV)
	{
		const ctorQ = Quaternion.instance.getQuaternionConstructor(managedQ) ;
		const ctorV = Quaternion.instance.getVectorConstructor(managedV) ;

		const q = new ctorQ (0.7071068, 0, 0, 0.7071068) ;
		const x = new ctorV (1, 0, 0) ;
		const y = new ctorV (0, 1, 0) ;
		const z = new ctorV (0, 0, 1) ;

		const checkVector = function (a, b)
		{
			nkDebug.TestUtils.areNumbersEqual(a._x, b._x, 0.0001, "Bad X") ;
			nkDebug.TestUtils.areNumbersEqual(a._y, b._y, 0.0001, "Bad Y") ;
			nkDebug.TestUtils.areNumbersEqual(a._z, b._z, 0.0001, "Bad Z") ;
			nkDebug.TestUtils.areNumbersEqual(a._w, b._w, 0.0001, "Bad W") ;
		} ;

		checkVector(q.transform(x), x) ;
		checkVector(q.transform(y), z) ;
		checkVector(q.transform(z), new nkMaths.Vector (0, -1, 0)) ;

		if (!managedQ)
			q.delete() ;

		if (!managedV)
		{
			x.delete() ;
			y.delete() ;
			z.delete() ;
		}
	}

	testTransformVectorDualReturns (managed)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const a = new ctor () ;
		const b = new nkMaths.Vector () ;

		const rManaged = a.transform(b) ;
		nkDebug.TestUtils.check(!nk.isUnmanagedInstance(rManaged), "Unmanaged should be managed") ;

		const rUnmanaged = a.transform_u(b) ;
		nkDebug.TestUtils.check(nk.isUnmanagedInstance(rUnmanaged), "Managed should be unmanaged") ;
		rUnmanaged.delete() ;
		
		if (!managed)
			a.delete() ;
	}

	// Operators
	testEqualsOperator (managed0, managed1)
	{
		const ctor0 = Quaternion.instance.getQuaternionConstructor(managed0) ;
		const ctor1 = Quaternion.instance.getQuaternionConstructor(managed1) ;
		const a = new ctor0 (5, 2, 6, 4) ;
		const b = new ctor1 (14, 30, 1, 2) ;
		const c = new ctor1 (14, 30, 1, 2) ;

		nkDebug.TestUtils.check(a.equals(a), "Incorrect equal result") ;
		nkDebug.TestUtils.check(!a.equals(b), "Incorrect equal result") ;
		nkDebug.TestUtils.check(!b.equals(a), "Incorrect equal result") ;
		nkDebug.TestUtils.check(!a.equals(c), "Incorrect equal result") ;
		nkDebug.TestUtils.check(!c.equals(a), "Incorrect equal result") ;
		nkDebug.TestUtils.check(b.equals(b), "Incorrect equal result") ;
		nkDebug.TestUtils.check(c.equals(b), "Incorrect equal result") ;
		nkDebug.TestUtils.check(b.equals(c), "Incorrect equal result") ;

		if (!managed0)
			a.delete() ;

		if (!managed1)
		{
			b.delete() ;
			c.delete() ;
		}
	}

	testAssignOperator (managed0, managed1)
	{
		const ctor0 = Quaternion.instance.getQuaternionConstructor(managed0) ;
		const ctor1 = Quaternion.instance.getQuaternionConstructor(managed1) ;
		const q0 = new ctor0 (2, 3, 4, 5) ;
		const q1 = new ctor1 (5, 4, 3, 2) ;

		q0.assign(q1) ;
		nkDebug.TestUtils.check(q0.equals(q1)) ;

		if (!managed0)
			q0.delete() ;

		if (!managed1)
			q1.delete() ;
	}

	testCloneOperator (managed)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const a = new ctor (2, 3, 4, 5) ;

		const b = a.clone() ;
		nkDebug.TestUtils.check(a.equals(b)) ;

		if (!managed)
			a.delete() ;
	}

	testCloneDualReturns (managed)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const a = new ctor () ;

		const rManaged = a.clone() ;
		nkDebug.TestUtils.check(!nk.isUnmanagedInstance(rManaged), "Unmanaged should be managed") ;

		const rUnmanaged = a.clone_u() ;
		nkDebug.TestUtils.check(nk.isUnmanagedInstance(rUnmanaged), "Managed should be unmanaged") ;
		rUnmanaged.delete() ;
		
		if (!managed)
			a.delete() ;
	}

	testMulOperator (managed0, managed1)
	{
		const ctor0 = Quaternion.instance.getQuaternionConstructor(managed0) ;
		const ctor1 = Quaternion.instance.getQuaternionConstructor(managed1) ;
		const a = new ctor0 (0.707106769, 0, 0, 0.707106769) ;
		const b = new ctor1 (0, 0.707106769, 0, 0.707106769) ;

		const ab = a.mul(b) ;
		nkDebug.TestUtils.areNumbersEqual(ab._x, 0.499999970, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(ab._y, 0.499999970, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(ab._z, 0.499999970, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(ab._w, 0.499999970, 0.0001, "Wrong W") ;

		const ba = b.mul(a) ;
		nkDebug.TestUtils.areNumbersEqual(ba._x, 0.499999970, 0.0001, "Wrong X") ;
		nkDebug.TestUtils.areNumbersEqual(ba._y, 0.499999970, 0.0001, "Wrong Y") ;
		nkDebug.TestUtils.areNumbersEqual(ba._z, -0.499999970, 0.0001, "Wrong Z") ;
		nkDebug.TestUtils.areNumbersEqual(ba._w, 0.499999970, 0.0001, "Wrong W") ;

		if (!managed0)
			a.delete() ;

		if (!managed1)
			b.delete() ;
	}

	testMulDualReturns (managed)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const a = new ctor () ;
		const b = new ctor () ;

		const rManaged = a.mul(b) ;
		nkDebug.TestUtils.check(!nk.isUnmanagedInstance(rManaged), "Unmanaged should be managed") ;

		const rUnmanaged = a.mul_u(b) ;
		nkDebug.TestUtils.check(nk.isUnmanagedInstance(rUnmanaged), "Managed should be unmanaged") ;
		rUnmanaged.delete() ;

		if (!managed)
		{
			a.delete() ;
			b.delete() ;
		}
	}
	
	testMulVectorOperator (managedQ, managedV)
	{
		const ctorQ = Quaternion.instance.getQuaternionConstructor(managedQ) ;
		const ctorV = Quaternion.instance.getVectorConstructor(managedV) ;

		const q = new ctorQ (0.7071068, 0, 0, 0.7071068) ;
		const x = new ctorV (1, 0, 0) ;
		const y = new ctorV (0, 1, 0) ;
		const z = new ctorV (0, 0, 1) ;

		const checkVector = function (a, b)
		{
			nkDebug.TestUtils.areNumbersEqual(a._x, b._x, 0.0001, "Bad X") ;
			nkDebug.TestUtils.areNumbersEqual(a._y, b._y, 0.0001, "Bad Y") ;
			nkDebug.TestUtils.areNumbersEqual(a._z, b._z, 0.0001, "Bad Z") ;
			nkDebug.TestUtils.areNumbersEqual(a._w, b._w, 0.0001, "Bad W") ;
		} ;

		checkVector(q.mulVector(x), x) ;
		checkVector(q.mulVector(y), z) ;
		checkVector(q.mulVector(z), new nkMaths.Vector (0, -1, 0)) ;

		if (!managedQ)
			q.delete() ;

		if (!managedV)
		{
			x.delete() ;
			y.delete() ;
			z.delete() ;
		}
	}

	testMulVectorDualReturns (managed)
	{
		const ctor = Quaternion.instance.getQuaternionConstructor(managed) ;
		const a = new ctor () ;
		const b = new nkMaths.Vector () ;

		const rManaged = a.mulVector(b) ;
		nkDebug.TestUtils.check(!nk.isUnmanagedInstance(rManaged), "Unmanaged should be managed") ;

		const rUnmanaged = a.mulVector_u(b) ;
		nkDebug.TestUtils.check(nk.isUnmanagedInstance(rUnmanaged), "Managed should be unmanaged") ;
		rUnmanaged.delete() ;

		if (!managed)
			a.delete() ;
	}

	nkTests =
	{
		// Constructors
		DefaultConstructorUnmanaged : function ()
		{
			Quaternion.instance.testDefaultConstructor(false) ;
		},
		DefaultConstructorManaged : function ()
		{
			Quaternion.instance.testDefaultConstructor(true) ;
		},
		ArgsConstructorUnmanaged : function ()
		{
			Quaternion.instance.testArgsConstructor(false) ;
		},
		ArgsConstructorManaged : function ()
		{
			Quaternion.instance.testArgsConstructor(true) ;
		},
		AxisAngleConstructorUnmanaged : function ()
		{
			Quaternion.instance.testAxisAngleConstructor(false, false) ;
		},
		AxisAngleConstructorUnmanagedManaged : function ()
		{
			Quaternion.instance.testAxisAngleConstructor(false, true) ;
		},
		AxisAngleConstructorManagedUnmanaged : function ()
		{
			Quaternion.instance.testAxisAngleConstructor(true, false) ;
		},
		AxisAngleConstructorManaged : function ()
		{
			Quaternion.instance.testAxisAngleConstructor(true, true) ;
		},
		EulerConstructorUnmanaged : function ()
		{
			Quaternion.instance.testEulerConstructor(false, false) ;
		},
		EulerConstructorUnmanagedManaged : function ()
		{
			Quaternion.instance.testEulerConstructor(false, true) ;
		},
		EulerConstructorManagedUnmanaged : function ()
		{
			Quaternion.instance.testEulerConstructor(true, false) ;
		},
		EulerConstructorManaged : function ()
		{
			Quaternion.instance.testEulerConstructor(true, true) ;
		},
		MatrixConstructorUnmanaged : function ()
		{
			Quaternion.instance.testMatrixConstructor(false, false) ;
		},
		MatrixConstructorUnmanagedManaged : function ()
		{
			Quaternion.instance.testMatrixConstructor(false, true) ;
		},
		MatrixConstructorManagedUnmanaged : function ()
		{
			Quaternion.instance.testMatrixConstructor(true, false) ;
		},
		MatrixConstructorManaged : function ()
		{
			Quaternion.instance.testMatrixConstructor(true, true) ;
		},
		CopyConstructorUnmanaged : function ()
		{
			Quaternion.instance.testCopyConstructor(false, false) ;
		},
		CopyConstructorUnmanagedManaged : function ()
		{
			Quaternion.instance.testCopyConstructor(false, true) ;
		},
		CopyConstructorManagedUnmanaged : function ()
		{
			Quaternion.instance.testCopyConstructor(true, false) ;
		},
		CopyConstructorManaged : function ()
		{
			Quaternion.instance.testCopyConstructor(true, true) ;
		},

		// Getters
		GetAsEulerUnmanaged : function ()
		{
			Quaternion.instance.testGetAsEuler(false) ;
		},
		GetAsEulerManaged : function ()
		{
			Quaternion.instance.testGetAsEuler(true) ;
		},

		// Setters
		SetFromEulerUnmanaged : function ()
		{
			Quaternion.instance.testSetFromEuler(false, false) ;
		},
		SetFromEulerUnmanagedManaged : function ()
		{
			Quaternion.instance.testSetFromEuler(false, true) ;
		},
		SetFromEulerManagedUnmanaged : function ()
		{
			Quaternion.instance.testSetFromEuler(true, false) ;
		},
		SetFromEulerManaged : function ()
		{
			Quaternion.instance.testSetFromEuler(true, true) ;
		},
		SetFromAxisAngleUnmanaged : function ()
		{
			Quaternion.instance.testSetFromAxisAngle(false, false) ;
		},
		SetFromAxisAngleUnmanagedManaged : function ()
		{
			Quaternion.instance.testSetFromAxisAngle(false, true) ;
		},
		SetFromAxisAngleManagedUnmanaged : function ()
		{
			Quaternion.instance.testSetFromAxisAngle(true, false) ;
		},
		SetFromAxisAngleManaged : function ()
		{
			Quaternion.instance.testSetFromAxisAngle(true, true) ;
		},
		SetFromMatrixUnmanaged : function ()
		{
			Quaternion.instance.testSetFromMatrix(false, false) ;
		},
		SetFromMatrixUnmanagedManaged : function ()
		{
			Quaternion.instance.testSetFromMatrix(false, true) ;
		},
		SetFromMatrixManagedUnmanaged : function ()
		{
			Quaternion.instance.testSetFromMatrix(true, false) ;
		},
		SetFromMatrixManaged : function ()
		{
			Quaternion.instance.testSetFromMatrix(true, true) ;
		},

		// Transformations
		TransformVectorUnmanaged : function ()
		{
			Quaternion.instance.testTransformVector(false, false) ;
		},
		TransformVectorUnmanagedManaged : function ()
		{
			Quaternion.instance.testTransformVector(false, true) ;
		},
		TransformVectorManagedUnmanaged : function ()
		{
			Quaternion.instance.testTransformVector(true, false) ;
		},
		TransformVectorManaged : function ()
		{
			Quaternion.instance.testTransformVector(true, true) ;
		},
		TransformVectorDualReturnsUnmanaged : function ()
		{
			Quaternion.instance.testTransformVectorDualReturns(false) ;
		},
		TransformVectorDualReturnsManaged : function ()
		{
			Quaternion.instance.testTransformVectorDualReturns(true) ;
		},

		// Operators
		EqualsOperatorUnmanaged : function ()
		{
			Quaternion.instance.testEqualsOperator(false, false) ;
		},
		EqualsOperatorUnmanagedManaged : function ()
		{
			Quaternion.instance.testEqualsOperator(false, true) ;
		},
		EqualsOperatorManagedUnmanaged : function ()
		{
			Quaternion.instance.testEqualsOperator(true, false) ;
		},
		EqualsOperatorManaged : function ()
		{
			Quaternion.instance.testEqualsOperator(true, true) ;
		},
		AssignOperatorUnmanaged : function ()
		{
			Quaternion.instance.testAssignOperator(false, false) ;
		},
		AssignOperatorUnmanagedManaged : function ()
		{
			Quaternion.instance.testAssignOperator(false, true) ;
		},
		AssignOperatorManagedUnmanaged : function ()
		{
			Quaternion.instance.testAssignOperator(true, false) ;
		},
		AssignOperatorManaged : function ()
		{
			Quaternion.instance.testAssignOperator(true, true) ;
		},
		CloneOperatorManagedUnmanaged : function ()
		{
			Quaternion.instance.testCloneOperator(false) ;
		},
		CloneOperatorManaged : function ()
		{
			Quaternion.instance.testCloneOperator(true) ;
		},
		CloneDualReturnsUnmanaged : function ()
		{
			Quaternion.instance.testCloneDualReturns(false) ;
		},
		CloneDualReturnsManaged : function ()
		{
			Quaternion.instance.testCloneDualReturns(true) ;
		},
		MulOperatorUnmanaged : function ()
		{
			Quaternion.instance.testMulOperator(false, false) ;
		},
		MulOperatorUnmanagedManaged : function ()
		{
			Quaternion.instance.testMulOperator(false, true) ;
		},
		MulOperatorManagedUnmanaged : function ()
		{
			Quaternion.instance.testMulOperator(true, false) ;
		},
		MulOperatorManaged : function ()
		{
			Quaternion.instance.testMulOperator(true, true) ;
		},
		MulDualReturnsUnmanaged : function ()
		{
			Quaternion.instance.testMulDualReturns(false) ;
		},
		MulDualReturnsManaged : function ()
		{
			Quaternion.instance.testMulDualReturns(true) ;
		},
		MulVectorOperatorUnmanaged : function ()
		{
			Quaternion.instance.testMulVectorOperator(false, false) ;
		},
		MulVectorOperatorUnmanagedManaged : function ()
		{
			Quaternion.instance.testMulVectorOperator(false, true) ;
		},
		MulVectorOperatorManagedUnmanaged : function ()
		{
			Quaternion.instance.testMulVectorOperator(true, false) ;
		},
		MulVectorOperatorManaged : function ()
		{
			Quaternion.instance.testMulVectorOperator(true, true) ;
		},
		MulVectorDualReturnsUnmanaged : function ()
		{
			Quaternion.instance.testMulVectorDualReturns(false) ;
		},
		MulVectorDualReturnsManaged : function ()
		{
			Quaternion.instance.testMulVectorDualReturns(true) ;
		},
	}
}